在前一天,我們將網站爬蟲已經實做了,在今日我們要將在回應回來的網頁內容中的每個收盤價檔案給找到並下載回來。
首先,先將爬蟲相關用到的開發環境跑起來:
可以使用下面的指令將相關環境跑起來:
docker run --name=php_crawler -d -it php_crawler bash
接著,打開自己偏好使用的程式編輯器,將「lab3-1-closing-price.php」檔案打開並新增下面的內容:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;
$closingPriceLink = 'https://www.kgieworld.com.tw/Stock/stock_2_7.aspx?findex=1';
$client = new Client(['cookies' => true]);
$response = $client->request('GET', $closingPriceLink);
$responseString = (string)$response->getBody();
$viewState = '__VIEWSTATE';
$eventValidation = '__EVENTVALIDATION';
$viewStateGenerator = '63FF896A';
$closingPriceFileContents = [
'lbtnDown01',
'lbtnDown02',
'lbtnDown03',
'lbtnDown04',
'lbtnDown05',
];
$closingPriceDates = [
'lblDate01' => '',
'lblDate02' => '',
'lblDate03' => '',
'lblDate04' => '',
'lblDate05' => '',
];
$crawler = new Crawler($responseString);
$crawler
->filter('input[type="hidden"]')
->reduce(function (Crawler $node, $i) {
global $viewState;
global $eventValidation;
if ($node->attr('name') === $viewState) {
$viewState = $node->attr('value');
}
if ($node->attr('name') === $eventValidation) {
$eventValidation = $node->attr('value');
}
});
foreach ($closingPriceDates as $btnDateKey => $btnDate) {
$crawler
->filter('span[id="' . $btnDateKey . '"]')
->reduce(function (Crawler $node, $i) {
global $closingPriceDates;
global $btnDateKey;
$closingPriceDates[$btnDateKey] = $node->text();
});
}
$formParams = [
'form_params' => [
'__EVENTTARGET' => '',
'__EVENTARGUMENT' => '',
'__VIEWSTATE' => $viewState,
'__VIEWSTATEGENERATOR' => $viewStateGenerator,
'__EVENTVALIDATION' => $eventValidation,
'selMarket' => '1',
'T1' => '',
'T1' => '',
],
'headers' => [
'Host' => 'www.kgieworld.com.tw',
'Upgrade-Insecure-Requests' => '1',
'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Sec-Fetch-Mode' => 'navigate',
'Sec-Fetch-User' => '?1',
'Sec-Fetch-Site' => 'same-origin',
],
];
$index = 1;
foreach ($closingPriceFileContents as $eventTarget) {
$formParams['form_params']['__EVENTTARGET'] = $eventTarget;
$response = $client->request('POST', $closingPriceLink, $formParams);
$closingPriceFileContent = (string)$response->getBody();
$closingPriceFileName = $closingPriceDates['lblDate0' . $index] . '.csv';
$fileHandler = fopen($closingPriceFileName, 'w');
$closingPriceCrawler = new Crawler($closingPriceFileContent);
$closingPriceCrawler
->filter('tr')
->reduce(function (Crawler $node, $i) {
global $fileHandler;
$texts = str_replace(["\r", "\n", " ", " ", "'", "amp;", '<td>'], '', $node->html());
$texts = str_replace('</td>', ',', $texts);
$texts = mb_substr($texts, 0, -1) . "\n";
fputs($fileHandler, $texts);
});
fclose($fileHandler);
$index += 1;
}
我們可以發現到,新增從「$index = 1;」這行開始以下的程式碼,這段程式碼能將上面拿到的回應網頁內容中,擷取出每個日期對應的收盤價檔案。
但是從網頁下載的收盤價檔案可以發現一件事情,其情形如下:
利用「file」指令可以看到此下載回來的其中一個收盤價檔案資訊:
/home/lee/Downloads/20191009.xls: HTML document, UTF-8 Unicode text, with CRLF line terminators
副檔名雖然是「xls」但是內容是HTML
內容,打開來之後是一個表格的內容,相關內容如下:
<table cellspacing="0" rules="all" border="1" id="gvExport" style="font-family:標楷體;border-collapse:collapse;">
......
</table>
很顯然是,每個tr
標籤中是一個row
,那我們需要自己解析出這些標籤內容,自行轉換成CSV
檔案的格式。
這些轉換與解析程式已經實做在上述的程式內容中了。接著,我們在上述執行的Docker
容器中執行此程式,會得到下面的結果。
依序執行下面的指令:
docker cp lab3-1-closing-price.php php_crawler:/root/
docker exec -it php_crawler php lab3-1-closing-price.php
接著,會得到下列這幾個檔案,隨著每日不同得到的收盤價日期檔案也會有不同,此網站後端只保留最新開盤日期距離前五天的收盤價資料而已。
接著會看到多了5
個CSV
檔案。
docker exec -it php_crawler ls /root/
2019-10-03.csv 2019-10-08.csv composer.lock vendor
2019-10-04.csv 2019-10-09.csv composer.phar
2019-10-07.csv composer.json lab3-1-closing-price.php
在每個檔案內容中,會有一些不必要的字元,像是有'
,還有&
會轉成HTML
特殊字元(&
)等。
為了這些的內容,我們需要用字串取代的函式將其內容做一些轉換,讓輸出的CSV
檔案內容有一個比較好的結果與呈現。
從上面的實做程式,我們可以知道完成了幾件事情:
xls
,也不是一個CSV
檔案,而是table
組成的檔案內容table
組成的HTML
標籤轉換成CSV
檔案格式並匯出相對應的收盤價CSV
檔案到這裡,基本上已經完成所有的案例研討了,經過這幾天下來,相信讀者學會了:
那在剩下的課程5
天中,將會著重在設計與實做先前這些網站的案例整合上,敬請期待!